from weakref import WeakValueDictionary

from pyglet.event import EventDispatcher
from windoh.style import styledict


class childlist(list, EventDispatcher):
    def __delitem__(self, index):
        other = self[index]
        list.__delitem__(self, index)
        self.dispatch_event('on_remove', other)
        other.dispatch_event('on_removed')
        self._named_children = WeakValueDictionary()

    def __setitem__(self, other):
        raise NotImplementedError("you cannot __setitem__ a control")

    def named(self, name):
        try:
            return self._named_children[name]
        except KeyError:
            for child in self:
                if child.name == name:
                    self._named_children[name] = child
                    return child
            raise

    def append(self, other):
        list.append(self, other)
        self.dispatch_event('on_append', other)
        other.dispatch_event('on_appended')

    def extend(self, others):
        [self.append(o) for o in others]

    def remove(self, other):
        idx = self.index(other)
        del self[idx]
        
childlist.register_event_type('on_append')
childlist.register_event_type('on_remove')


class Control(EventDispatcher):
    def __init__(self, name=None, classname=None, style=None, renderer=None, **kwargs):
        self.name = name
        self.classname = classname
        self._children = childlist()
        self._children.push_handlers(
            on_append=self.on_child_append,
            on_remove=self.on_child_remove,
        )
        self._children_proxy = self._children

        if style is None:
            self.style = styledict(**kwargs)
        else:
            self.style.update(kwargs)
        self.style.push_handlers(self)
        
        self._root = None
        self._renderer = renderer
        self._parent = None
        self._dirty = False
        self._being_dragged = False
        self._hover = False

        self._computed_style = None

    def __getstate__(self):
        d = self.__dict__.copy()
        if d['name'] is None:
            del d['name']
        if d['classname'] is None:
            del d['classname']
        ret = {}
        for k in d.iterkeys():
            if k[0] != '_':
                ret[k] = d[k]
        return ret

    def __repr__(self):
        return "<%s name='%s' classname='%s'>" % (self.__class__.__name__,
                    self.name, self.classname)

    def __eq__(self, other):
        return self is other

    def append(self, other):
        self._children_proxy.append(other)

    def extend(self, others):
        self._children_proxy.extend(others)
    
    def remove(self, other):
        self._children_proxy.remove(other)

    def __getitem__(self, name):
        return self._children_proxy.named(name)

    # copied directly from pyglet so we can add better return values
    def dispatch_event(self, event_type, *args):
        '''Dispatch a single event to the attached handlers.

        The event is propogated to all handlers from from the top of the stack
        until one returns `EVENT_HANDLED`.  This method should be used only by
        `EventDispatcher` implementors; applications should call
        the ``dispatch_events`` method.

        :Parameters:
            `event_type` : str
                Name of the event.
            `args` : sequence
                Arguments to pass to the event handler.

        '''
        try:
            assert event_type in self.event_types
        except AssertionError:
            print event_type
            raise

        # Search handler stack for matching event handlers
        for frame in list(self._event_stack):
            handler = frame.get(event_type, None)
            if handler:
                try:
                    if handler(*args):
                        return True
                except TypeError:
                    self._raise_dispatch_exception(event_type, args, handler)

        d = event_type in ('on_mouse_over', 'on_mouse_out')
        d = False

        # Check instance for an event handler
        if hasattr(self, event_type):
            try:
                f = getattr(self, event_type)
            except TypeError:
                self._raise_dispatch_exception(
                    event_type, args, getattr(self, event_type))
            else:
                if d: 
                    print f
                    print f.im_func.func_code
                return f(*args)

        return False

    def dispatch_mouse_event(self, event_type, x, y, *args):
        if self.dispatch_event('on_pre'+event_type[2:], x, y, *args):
            return True

        cs = self._computed_style

        for child in reversed(self._children):
            ccs = child._computed_style
            if cs is None:
                continue
            if (x > ccs['left'] and 
                y > ccs['bottom'] and
                x < ccs['left'] + ccs['width'] and
                y < ccs['bottom'] + ccs['height']):
                if child.dispatch_mouse_event(event_type, 
                    x-ccs['left'], y-ccs['bottom'], *args):
                    return True
        try:
            return self.dispatch_event(event_type, x, y, *args)
        except AssertionError:
            print event_type, args
            raise
 
    def on_style_change(self, attrs):
        attrset = set(attrs.keys())
        self.set_dirty()

    """
    def on_appended(self, parent):
        self.parent = parent
        self.root = parent.root
        for child in self:
            child.dispatch_event('on_appended', self)

    def on_removed(self, parent):
        self.parent = None
    """

    def _update_root(self, root):
        self._root = root
        for child in self._children:
            child._update_root(root)

    def on_child_append(self, other):
        other._parent = self
        other._update_root(self._root)

    def on_child_remove(self, other):
        other._parent = None
        other._root = None
    

    def on_start_drag(self):
        self._being_dragged = True

    def on_stop_drag(self):
        self._being_dragged = False

    def on_mouse_motion(self, x, y, dx, dy):
        if not self._hover:
            self._root.dispatch_event('on_mouse_over', self)
        return True

    #def on_child_append(self, child):
    #    if child.name:
    #        self._named_children[child.name] = child

    #def on_child_removed(self, child):
    #    if child.name:
    #        del self._named_children[child.name]

    def on_mouse_over(self):
        if self._computed_style['cursor']:
            self._root.set_cursor(self._computed_style['cursor'])
        self._hover = True       

    def on_mouse_out(self):
        self._hover = False


    #def append(self, other):
    #    assert(isinstance(other, Control))
    #    list.append(self, other)
    #    self.dispatch_event('on_child_append', other)
    #    other.dispatch_event('on_appended', self)

    #def extend(self, others):
    #    [self.append(o) for o in others]

    #def remove(self, other):
    #    print "remove", self, other
    #    list.remove(self, other)
    #    self.dispatch_event('on_child_remove', other)
    #    other.dispatch_event('on_removed', self)


    def render(self):
        self._renderer.render(self)

    def set_dirty(self):
        # we really do have to make everything dirty if you think about it ...
        # until someone writes some clever code here :]
        #self.computed_style = None
        self._dirty = True
        self.dispatch_event('on_dirty')

    def on_dirty(self):
        for child in self._children:
            child.set_dirty()

    def focus(self):
        self._root.dispatch_event('on_focus_changed', self)

    def blur(self):
        self._root.dispatch_event('on_focus_lost', self)

    def on_compute_style(self):
        self._root._base_style.compute(self)
 
    def start_drag(self):
        self._root.dispatch_event('on_start_drag', self)

    def stop_drag(self):
        self._root.dispatch_event('on_stop_drag', self)

    def bring_to_front(self):
        if self._parent._children[-1] != self:
            p = self._parent
            p._children.remove(self)
            p._children.append(self)


Control.register_event_type('on_dirty')

Control.register_event_type('on_appended')
Control.register_event_type('on_removed')

Control.register_event_type("on_mouse_press")
Control.register_event_type("on_mouse_release")
Control.register_event_type("on_mouse_motion")
Control.register_event_type("on_pre_mouse_press")
Control.register_event_type("on_pre_mouse_release")
Control.register_event_type("on_pre_mouse_motion")
Control.register_event_type("on_mouse_over")
Control.register_event_type("on_mouse_out")

Control.register_event_type("on_key_press")
Control.register_event_type("on_key_release")
Control.register_event_type("on_text")

Control.register_event_type("on_focus")
Control.register_event_type("on_blur")

Control.register_event_type("on_start_drag")
Control.register_event_type("on_stop_drag")
Control.register_event_type("on_drag")

Control.register_event_type("on_compute_style")

    
